package com.oocl.scan.handler;

import com.oocl.scan.dao.impl.ComponentDaoImpl;
import com.oocl.scan.dao.impl.SoapConfigDaoImpl;
import com.oocl.scan.db.ArangoDBConnection;
import com.oocl.scan.jdt.parser.StatementParser;
import com.oocl.scan.model.BuildRelationship;
import com.oocl.scan.model.Component;
import com.oocl.scan.model.JavaClassElement;
import com.oocl.scan.model.Relationship;
import com.oocl.scan.model.SoapConfig;
import com.oocl.scan.utils.ScanConstants;
import java.util.ArrayList;
import java.util.*;
import java.util.Map.Entry;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.w3c.dom.NodeList;

public class MethodRelationHandler extends BaseHandler{

  private ExecutorService threadPool = Executors.newFixedThreadPool(10);

  @Override
  public void handle(JavaClassElement element) {
    element.buildImportMap();
    analyzeMe(element);
//    shutdown();
//    ArangoDBConnection.shutdown();
  }


  public void shutdown() {
    threadPool.shutdown();
    while (true) {
      if (threadPool.isTerminated()) {
        logger.info("====================================== Method Relation Handler Scan Done ======================================");
        break;
      }
    }
  }

  private void analyzeMe(JavaClassElement element) {
    List<MethodDeclaration> methodDeclarationList = element.getMethodDeclarationList();
    for(MethodDeclaration methodDeclaration : methodDeclarationList){
      threadPool.execute(new Runnable() {
        @Override
        public void run() {
          JavaClassElement javaClassElement = element;
          BuildRelationship buildRelationship = new BuildRelationship();
          buildRelationship.setFromModule(javaClassElement.getModuleName());
          buildRelationship.setFromClassFullName(javaClassElement.getJavaClassFullName());
          buildRelationship.setFrom(buildRelationship.getFromClassFullName() + "-" + methodDeclaration.getName());
          buildMethodAndVariableStatement(methodDeclaration.getBody(), buildRelationship);
          analysisMethod(javaClassElement, buildRelationship);
        }
      });
    }
  }

  private void analysisMethod(JavaClassElement element, BuildRelationship buildRelationship) {
    try {
      Set<Entry<String, List<String>>> methodInvocationSet = buildRelationship.getMethodInvocationMap().entrySet();
      Iterator<Entry<String, List<String>>> iterator = methodInvocationSet.iterator();
      while (iterator.hasNext()) {
        Map.Entry<String, List<String>> methodInvocation = iterator.next();
        String variableName = methodInvocation.getKey();
        List<String> methodNameList = methodInvocation.getValue();
        Object callSubMethodClassName = "";
        if ("".equals(variableName)) {
          for (String method : methodNameList) {
            if (isInnerClassMethod(element, method)) {
              buildToName(method, element.getJavaClassFullName(), element, buildRelationship);
              buildRelationship(buildRelationship);
            }
          }
        } else {
          callSubMethodClassName = getSubMethodCalledClassName(buildRelationship.getVariableMap(), variableName, element);
          String callSubMethodFullClassName = getSubMethodCalledName(callSubMethodClassName, element);
          if (callSubMethodFullClassName != null) {
            for (String method : methodNameList) {
              buildToName(method, callSubMethodFullClassName, element, buildRelationship);
              buildRelationship(buildRelationship);
            }
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private boolean isInnerClassMethod(JavaClassElement element, String methodName) {
    List<MethodDeclaration> methodDeclarationList = element.getMethodDeclarationList();
    for (MethodDeclaration methodDeclaration : methodDeclarationList) {
      if (methodName.equals(methodDeclaration.getName().toString())) {
        return true;
      }
    }
    return false;
  }

  private void buildToName(String method, String callSubMethodFullClassName, JavaClassElement element, BuildRelationship buildRelationship) {
    buildRelationship.setTo(callSubMethodFullClassName + "-" + method);
    if (isController(element)) {
      if ("".equals(buildRelationship.getProxyPath())) {
        SoapConfig soapConfig = new SoapConfigDaoImpl().getOriginByDtoName(callSubMethodFullClassName);
        if (soapConfig != null) {
          buildRelationship.setTo(soapConfig.getResourceName() + "-" + method);
        }
      } else {
        List<SoapConfig> soapConfigList = new SoapConfigDaoImpl().getByDtoName(callSubMethodFullClassName);
        for (int i = 0; i < soapConfigList.size(); i++) {
          if (soapConfigList.get(i) != null && soapConfigList.get(i).getResourceName() != null && buildRelationship.getProxyPath() != null) {
            if (soapConfigList.get(i).getSoapConfigURL().endsWith(buildRelationship.getProxyPath())) {
              buildRelationship.setTo(soapConfigList.get(i).getResourceName() + "-" + method);
              break;
            }
          }
        }
      }
    }
  }

  private void buildRelationship(BuildRelationship buildRelationship) {

    Component component = new ComponentDaoImpl().findByName(buildRelationship.getTo());
    if (component == null) {
      return;
    }

    Relationship relationship = new Relationship();
    relationship.setFromModule(buildRelationship.getFromModule());
    relationship.setFrom(buildRelationship.getFrom());
    relationship.setType(ScanConstants.RELATION_TYPE_FLOWS);
    relationship.setToModule(component.getModule());
    relationship.setTo(buildRelationship.getTo());
    addToRelationshipStore(relationship);
  }

  private String getSubMethodCalledName(Object className, JavaClassElement element) throws Exception {
    if (className == null) {
      return null;
    }

    if (ScanConstants.UNKNOWN.equals(className)) {
      return null;
    }

    if (className.toString().contains(".")) {
      return className.toString();
    } else {
      return element.findPackageNameByClass(className.toString());
    }
  }

  private Object getSubMethodCalledClassName(Map<String, Object> variableMap, String variableName, JavaClassElement element) throws Exception {
    Object className;
    if (isClassName(variableName.substring(0, 1).toCharArray()[0])) {
      className = variableName;
    } else {
      className = element.findClassNameByVariable(variableName, variableMap);
      if (className instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) className;
        Type argumentType = (Type) parameterizedType.typeArguments().get(0);
        if (argumentType instanceof SimpleType) {
          SimpleType simpleType = (SimpleType) argumentType;
          className = simpleType.getName().getFullyQualifiedName();
        }
      }
    }
    return className;
  }

  private boolean isClassName(char c) {
    return c >= 'A' && c <= 'Z';
  }

  private void buildMethodAndVariableStatement(Statement statementParam, BuildRelationship buildRelationship) {
    StatementParser statementHandler = new StatementParser();
    List<Statement> allStatement = new ArrayList<>();
    List<Expression> expressionList = new ArrayList<>();
    statementHandler.parseStatement(statementParam, allStatement, expressionList, buildRelationship.getVariableMap());
    allStatement.forEach((statement -> {
      if (statement instanceof VariableDeclarationStatement) {
        analysisVariableDeclaration((VariableDeclarationStatement) statement, buildRelationship);
      }
      if (statement instanceof ExpressionStatement) {
        ExpressionStatement expressionStatement = (ExpressionStatement) statement;
        Expression expression = expressionStatement.getExpression();
        if (expression instanceof Assignment) {
          Assignment assignment = (Assignment) expression;
          expression = assignment.getRightHandSide();
        }
        getMethodInvocation(buildRelationship.getMethodInvocationMap(), expression);
      }
      if (statement instanceof ReturnStatement) {
        ReturnStatement returnStatement = (ReturnStatement) statement;
        Expression expression = returnStatement.getExpression();
        getMethodInvocation(buildRelationship.getMethodInvocationMap(), expression);
      }
    }));

    expressionList.forEach((expression -> {
      if (expression instanceof Assignment) {
        Assignment assignment = (Assignment) expression;
        expression = assignment.getRightHandSide();
      }
      getMethodInvocation(buildRelationship.getMethodInvocationMap(), expression);
    }));
  }

  private void analysisVariableDeclaration(VariableDeclarationStatement statement, BuildRelationship buildRelationship) {
    VariableDeclarationStatement variableDeclarationStatement = statement;
    Type type = variableDeclarationStatement.getType();
    Object variableClassName = variableDeclarationStatement.getType().toString();
    if (type instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) type;
      variableClassName = parameterizedType.getType().toString();
      if (variableClassName.toString().contains("FWRESTServiceProxy")) {
        Type argumentType = (Type) parameterizedType.typeArguments().get(0);
        if (argumentType instanceof SimpleType) {
          SimpleType simpleType = (SimpleType) argumentType;
          variableClassName = simpleType.getName().getFullyQualifiedName();
          String path = variableDeclarationStatement.fragments().get(0).toString();
          if (path.indexOf(",") != -1) {
            buildRelationship.setProxyPath(path.substring(path.indexOf(",") + 2, path.length() - 2));
          } else {
            buildRelationship.setProxyPath("");
          }
        }
      }
    }
    VariableDeclarationFragment fragment = (VariableDeclarationFragment) variableDeclarationStatement.fragments().get(0);
    Expression expression = fragment.getInitializer();
    if (expression != null) {
      getMethodInvocation(buildRelationship.getMethodInvocationMap(), expression);
    }
    buildRelationship.getVariableMap()
        .put(((VariableDeclarationFragment) variableDeclarationStatement.fragments().get(0)).getName().getIdentifier(), variableClassName);
  }

  private void getMethodInvocation(Map<String, List<String>> methodInvocationMap, Expression expression) {
    if (expression instanceof MethodInvocation) {
      MethodInvocation methodInvocation = (MethodInvocation) expression;
      String methodInvocationVariable = "";
      Expression methodExpress = methodInvocation.getExpression();
      visitConstant(methodInvocation);
      if (methodExpress instanceof SimpleName) {
        SimpleName simpleName = (SimpleName) methodExpress;
        methodInvocationVariable = simpleName.getIdentifier();

      }
      if (methodExpress instanceof FieldAccess) {
        FieldAccess fieldAccess = (FieldAccess) methodExpress;
        methodInvocationVariable = fieldAccess.getName().getIdentifier();
      }
      if (!methodInvocationMap.containsKey(methodInvocationVariable)) {
        methodInvocationMap.put(methodInvocationVariable, new ArrayList<>());
      }
      if (methodExpress instanceof MethodInvocation) {
        getMethodInvocation(methodInvocationMap, methodExpress);
      }

      if ("execute".equals(methodInvocation.getName().getIdentifier().toString())) {
        methodInvocationMap.get(methodInvocationVariable).add("read");
        methodInvocationMap.get(methodInvocationVariable).add("write");
        methodInvocationMap.get(methodInvocationVariable).add("execute");
      } else {
        methodInvocationMap.get(methodInvocationVariable).add(methodInvocation.getName().getIdentifier());
      }
    }
  }

  private void visitConstant(MethodInvocation methodInvocation){
    System.out.println(methodInvocation.getName());
    List argumentList = methodInvocation.arguments();
    Iterator iterator = argumentList.iterator();
    while(iterator.hasNext()){
      Object object = iterator.next();
      if(object instanceof SimpleType){
        System.out.println(((SimpleType)object).getName());
      }
    }

  }
}
